{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Python Peephole Optimizations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Peephole optimizations refer to a certain class of optimization strategies Python employs during any compilation phases."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Constant Expressions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see how Python reduces constant expressions for optimization purposes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def my_func():\n",
    "    a = 24 * 60\n",
    "    b = (1, 2) * 5\n",
    "    c = 'abc' * 3\n",
    "    d = 'ab' * 11\n",
    "    e = 'the quick brown fox' * 10\n",
    "    f = [1, 2] * 5\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None,\n",
       " 24,\n",
       " 60,\n",
       " 1,\n",
       " 2,\n",
       " 5,\n",
       " 'abc',\n",
       " 3,\n",
       " 'ab',\n",
       " 11,\n",
       " 'the quick brown fox',\n",
       " 10,\n",
       " 1440,\n",
       " (1, 2),\n",
       " (1, 2, 1, 2, 1, 2, 1, 2, 1, 2),\n",
       " 'abcabcabc')"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func.__code__.co_consts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see in the example above, `24 * 60` was pre-calculated and cached as a constant (`1440`).\n",
    "\n",
    "Similarly, `(1, 2) * 5` was cached as `(1, 2, 1, 2, 1, 2, 1, 2, 1, 2)` and `'abc' * 3` was cached as `abcabcabc`.\n",
    "\n",
    "On the other hand, note how `'the quick brown fox' * 10` was **not** pre-calculated (too long).\n",
    "\n",
    "Similarly `[1, 2] * 5` was not pre-calculated either since a list is *mutable*, and hence not a *constant*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Membership Tests"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In membership testing, optimizations are applied as can be seen below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def my_func():\n",
    "    if e in [1, 2, 3]:\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, 1, 2, 3, (1, 2, 3))"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func.__code__.co_consts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the mutable list `[1, 2, 3]` was converted to an immutable tuple. \n",
    "\n",
    "It is OK to do this here, since we are testing membership of the list **at that point in time**, hence it is safe to convert it to a tuple, which is more efficient than testing membership of a list."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the same way, set membership will be converted to frozen set membership:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def my_func():\n",
    "    if e in {1, 2, 3}:\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, 1, 2, 3, frozenset({1, 2, 3}))"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func.__code__.co_consts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In general, when you are writing your code, if you can use **set** membership testing, prefer that over a list or tuple - it is quite a bit more efficient.\n",
    "\n",
    "Let's do a small quick (and dirty) benchmark of this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']\n",
      "\n",
      "('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')\n",
      "\n",
      "{'l', 'p', 'x', 'R', 'j', 'S', 's', 'T', 'W', 'Y', 'Z', 'P', 'g', 'O', 'b', 'u', 'H', 'G', 'v', 'e', 'M', 'n', 'w', 't', 'Q', 'E', 'N', 'X', 'C', 'i', 'A', 'B', 'F', 'V', 'a', 'm', 'r', 'f', 'h', 'U', 'D', 'c', 'y', 'z', 'J', 'd', 'o', 'I', 'L', 'K', 'k', 'q'}\n"
     ]
    }
   ],
   "source": [
    "import string\n",
    "import time \n",
    "\n",
    "char_list = list(string.ascii_letters)\n",
    "char_tuple = tuple(string.ascii_letters)\n",
    "char_set = set(string.ascii_letters)\n",
    "\n",
    "print(char_list)\n",
    "print()\n",
    "print(char_tuple)\n",
    "print()\n",
    "print(char_set)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def membership_test(n, container):\n",
    "    for i in range(n):\n",
    "        if 'p' in container:\n",
    "            pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "list membership:  2.6035404184015434\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "membership_test(10000000, char_list)\n",
    "end = time.perf_counter()\n",
    "print('list membership: ', end-start)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tuple membership:  2.602491734651276\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "membership_test(10000000, char_tuple)\n",
    "end = time.perf_counter()\n",
    "print('tuple membership: ', end-start)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "set membership:  0.3743007599607324\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "membership_test(10000000, char_set)\n",
    "end = time.perf_counter()\n",
    "print('set membership: ', end-start)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, set membership tests run quite a bit faster - which is not surprising since they are basically dictionary-like objects, so hash maps are used for looking up an item to determine membership."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
